package com.swt.gap.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.csv.*;
import cn.hutool.core.text.csv.CsvReader;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.swt.gap.common.*;
import com.swt.gap.common.constant.CommonConstant;
import com.swt.gap.common.constant.GraphDbConstant;
import com.swt.gap.common.exception.NoMatchedTemplateException;
import com.swt.gap.common.file.FileFactory;
import com.swt.gap.common.file.FormFileData;
import com.swt.gap.common.graph.*;
import com.swt.gap.common.graph.Graph;
import com.swt.gap.config.ServerConfig;
import com.swt.gap.dao.*;
import com.swt.gap.model.entity.BankCardBin;
import com.swt.gap.model.graphmodel.FileFilterConfig;
import com.swt.gap.model.graphmodel.FileName;
import com.swt.gap.model.vo.graph.*;
import com.swt.gap.service.GraphDataTransferService;
import com.swt.gap.service.TaskFileService;
import com.swt.gap.service.TaskGraphData;
import com.swt.gap.util.*;
import com.swt.gap.util.excel.ExcelData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 请填写说明
 *
 * @author sx
 * @date 2020/7/20 17:02
 */
@Service
@Slf4j
public class GraphDataTransferServiceImpl implements GraphDataTransferService {

    private final static Integer PREVIEW_DATA_SIZE = 100;

    private final ServerConfig serverConfig;
    private final BankCardBinDao bankCardBinDao;
    private final SysIconMapper sysIconMapper;
    private final TaskGraphData taskGraphData;
    private final GraphProcessor graphProcessor;
    private final FileDao fileDao;
    private final TaskFileService taskFileService;

    public GraphDataTransferServiceImpl(ServerConfig serverConfig,
                                        BankCardBinDao bankCardBinDao,
                                        SysIconMapper sysIconMapper,
                                        TaskGraphData taskGraphData,
                                        GraphProcessor graphProcessor,
                                        FileDao fileDao,
                                        TaskFileService taskFileService) {
        this.serverConfig = serverConfig;
        this.bankCardBinDao = bankCardBinDao;
        this.sysIconMapper = sysIconMapper;
        this.taskGraphData = taskGraphData;
        this.graphProcessor = graphProcessor;
        this.fileDao = fileDao;
        this.taskFileService = taskFileService;
    }
    private static final CsvReadConfig csvReadConfig = new CsvReadConfig();

    static {
        csvReadConfig.setContainsHeader(true);
    }

    @Override
    public TransferGraphDataVO previewGraphData(PreviewGraphDataVO record) throws DateTimeParseException, NoMatchedTemplateException {
        String savedFileName = record.getSavedFileName();
        List<Map<String, String>> rowsData = new ArrayList<>();
        List<String> header = new ArrayList<>();
        if (record.getFileType().equals(FileTypeEnum.WORD07.getDataTypeCode())) {
            header = record.getTextContent().getHeader();
            for (int i = 0; i < record.getTextContent().getRows().size() && i < PREVIEW_DATA_SIZE; i++) {
                Map<String, String> rowData = new HashMap<>();
                for (int j = 0; j < header.size(); j++) {
                    rowData.put(header.get(j), record.getTextContent().getRows().get(i).get(j));
                }
                rowsData.add(rowData);
            }
        } else if (record.getFileType().equals(FileTypeEnum.CSV.getDataTypeCode())){
            String filePath = CommonConstant.UPLOAD_DIR + savedFileName;
            if (filePath.endsWith(FileTypeEnum.CSV.getSuffix())) {
                StopWatch stopwatch = StopWatch.createStarted();
                CsvReader reader = CsvUtil.getReader(csvReadConfig);
                CsvData data = reader.read(FileUtil.file(filePath), StandardCharsets.UTF_8);
                System.out.println("读取csv " + data.getRows().size() + "行 时间: " + stopwatch.getTime(TimeUnit.MILLISECONDS));
                header = data.getHeader();
                List<CsvRow> csvRows = data.getRows();
                for (int i = 0; i < csvRows.size() && i < PREVIEW_DATA_SIZE; i++) {
                    CsvRow csvRow = csvRows.get(i);
                    Map<String, String> rowData = csvRow.getFieldMap();
                    rowsData.add(rowData);
                }
                System.out.println("转换csv " + data.getRows().size() + "行 时间: " + stopwatch.getTime(TimeUnit.MILLISECONDS));
            } else if (filePath.endsWith(FileTypeEnum.EXCEL03.getSuffix()) || filePath.endsWith(FileTypeEnum.EXCEL07.getSuffix())) {
                ExcelData data = com.swt.gap.util.excel.ExcelUtil.getReader().read(filePath);
                header = data.getHeader();
                rowsData = data.getFieldsMap();
                if (rowsData.size() > 100) {
                    rowsData = rowsData.subList(0, 100);
                }
            }
        }

        if (!isMatchedHeader(header, record.getGraphModels())) {
            throw new NoMatchedTemplateException("表头与模板不匹配");
        }
        Graph graph = processFormData(record.getTaskId(), savedFileName, record.getGraphModels(), rowsData,
                record.getFileType(), record.getFieldFilterConfigList(), true);
        TransferGraphDataVO result = new TransferGraphDataVO();
        BeanUtils.copyProperties(graph, result);
        result.setOriginalDataSize(rowsData.size());
        result.setNodeSize(graph.getNodes().size());
        result.setLinkSize(graph.getLinks().size());
        return result;
    }

    @Override
    public TransferGraphDataVO importGraphData(ImportGraphDataVO record) throws DateTimeParseException, NoMatchedTemplateException {
        String savedFileName = record.getSavedFileName();
        List<Map<String, String>> rowsData = new ArrayList<>();
        List<String> header = new ArrayList<>();
        StopWatch stopwatch = StopWatch.createStarted();
        if (record.getFileType().equals(FileTypeEnum.WORD07.getDataTypeCode())) {
            header = record.getTextContent().getHeader();
            for (int i = 0; i < record.getTextContent().getRows().size(); i++) {
                Map<String, String> rowData = new HashMap<>();
                for (int j = 0; j < header.size(); j++) {
                    rowData.put(header.get(j), record.getTextContent().getRows().get(i).get(j));
                }
                rowsData.add(rowData);
            }
        } else {
            String filePath = CommonConstant.UPLOAD_DIR + savedFileName;
            FileFactory fileFactory = new FileFactory();
            FormFileData formFileData = fileFactory.getFile(filePath).readFormFile(filePath);
            header = formFileData.getHeader();
            rowsData = formFileData.getRowsData();
            System.out.println("读取csv " + rowsData.size() + "行 时间: " + stopwatch.getTime(TimeUnit.MILLISECONDS));
        }

        if (!isMatchedHeader(header, record.getGraphModels())) {
            throw new NoMatchedTemplateException("表头与模板不匹配");
        }
        Graph graph = processFormData(record.getTaskId(), savedFileName, record.getGraphModels(), rowsData,
                record.getFileType(), record.getFieldFilterConfigList(), false);
        System.out.println("处理时间： "  + stopwatch.getTime(TimeUnit.MILLISECONDS));
        System.out.println("row size ="  + rowsData.size() + " node size ="  + graph.getNodes().size() + " link size =" + graph.getLinks().size());

        importData(graph, record.getTaskId(), record.getFileId(), record.getModelPicContent(), record.getGraphModels());
        System.out.println("存储时间： "  + stopwatch.getTime(TimeUnit.MILLISECONDS));
        TransferGraphDataVO result = new TransferGraphDataVO();
        result.setOriginalDataSize(rowsData.size());
        result.setNodeSize(graph.getNodes().size());
        result.setLinkSize(graph.getLinks().size());
        return result;
    }

    @Override
    public TransferGraphData importEmlGraphData(ImportEmailGraphData record) {
        List<Email> emails = new ArrayList<>();
        List<String> files = Arrays.asList("测试邮件主题1.eml,测试邮件主题2.eml,主题3.eml,主题4.eml,Fw_测试邮件主题1.eml,Fw_测试邮件主题1.eml,Fw_测试邮件主题1 (1).eml".split(","));
        if (null != record.getSavedFileNames() && !record.getSavedFileNames().isEmpty()) {
            files = record.getSavedFileNames();
        }
        files.forEach(fileName -> {
            String filePath = CommonConstant.UPLOAD_DIR + fileName;
            Email email = EmailUtil.parseEmlFile(filePath);
            emails.add(email);
        });

        Graph graph = generateGraphDataByEmailData(record.getTaskId(), emails);

        importData(graph, record.getTaskId(), record.getFileId(), "", new ArrayList<>());
        Set<List<String>> taskLabels = new HashSet<>();
        taskLabels.add(Arrays.asList("实体,邮箱".split(",")));
        taskLabels.add(Arrays.asList("事件,发送邮件".split(",")));
        taskLabels.add(Arrays.asList("文档,附件".split(",")));
        taskGraphData.addTaskLabels(record.getTaskId(), taskLabels);
        TransferGraphData result = new TransferGraphData();
        BeanUtils.copyProperties(graph, result);
        if (null != record.getSavedFileNames()) {
            result.setOriginalDataSize(record.getSavedFileNames().size());
        }
        result.setNodeSize(graph.getNodes().size());
        result.setLinkSize(graph.getLinks().size());
        return result;
    }

    @Override
    public Integer importKB(Integer taskId, String fileName) throws IOException {


        String saveFileName = TimeFormatUtil.getCurrentTimeString() + "_" + fileName;


        int userId = UserLoginUtils.getUser().getUserId();
        com.swt.gap.model.entity.File file  = new com.swt.gap.model.entity.File();
        file.setCreatedBy(userId);
        file.setCreatedTime(new Date());
        file.setOriginalFileName(fileName);
        file.setFileType(4);
        file.setFileClassId(0);
        file.setIsDeleted(false);
        //数据来源
        file.setFileFromType(1);
        file.setSavedFileName(saveFileName);
//        file.setSavePath(CommonConstant.RELATIVE_DIR + saveFileName);
        fileDao.insertSelective(file);

        int fileId = file.getFileId();

        return taskFileService.addTaskFiles(taskId, Collections.singletonList(fileId));

    }


    public Graph processFormData(Integer taskId, String savedFileName, List<GraphModel> graphModels,
                                         List<Map<String, String>> rowsData, Integer fileType,
                                         List<FileFilterConfig.FieldFilterConfig> fieldFilterConfigList, Boolean isPreview) {
//        StopWatch stopwatch = StopWatch.createStarted();
        //用来去重
        Map<String, Graph.Node> mapNodes = new HashMap<>();
        Map<String, Graph.Link> mapLinks = new HashMap<>();

        // 文本文档数据 需要生成文件节点并关联
        Graph.Node fileNode = new Graph.Node();
        if (fileType.equals(FileTypeEnum.WORD07.getDataTypeCode())) {
            // 文件节点
            String nodeId = (String.valueOf(savedFileName.hashCode()));
            //根据labels获取icon
            String icon = serverConfig.parseViewUrl(sysIconMapper.getIconFileByLabels("文档,文本文档"), 3);

            Map<String, Object> retProperties = new HashMap<>();
            retProperties.put(GraphDbConstant.PROPERTY_KEY_TASK_ID, taskId);
            retProperties.put(GraphDbConstant.PROPERTY_KEY_ID, nodeId);
            retProperties.put(GraphDbConstant.PROPERTY_NAME, savedFileName.substring(savedFileName.indexOf("_") + 1));

            fileNode = Graph.Node.builder()
                    .id(nodeId)
                    .labels(Arrays.asList("文档,文本文档".split(",")))
                    .icon(icon)
                    .properties(retProperties)
                    .build();
        }

        for (Map<String, String> row : rowsData) {
            // 数据过滤
            boolean isMatched = true;
            if (null != fieldFilterConfigList) {
                for (FileFilterConfig.FieldFilterConfig fieldFilterConfig : fieldFilterConfigList) {
                    isMatched = StringFilter.isMatchCondition(row.get(fieldFilterConfig.getFiled()),
                            fieldFilterConfig.getValue(), fieldFilterConfig.getConditionType());
                    if (!isMatched) {
                        break;
                    }
                }
            }
            if (isMatched) {
                //交易事件关联节点 key是事件节点id，value是关联节点
                Map<String, List<Graph.Node>> transactionEventRelationNode = new HashMap<>();
                Map<String, List<Graph.Node>> callEventRelationNode = new HashMap<>();
                Graph.Node finalFileNode = fileNode;
                graphModels.forEach(graphModel -> {
                    NodeConfig startNodeConfig = graphModel.getStartNodeConfig();
                    NodeConfig endNodeConfig = graphModel.getEndNodeConfig();
                    RelationshipConfig relationshipConfig = graphModel.getRelationshipConfig();
                    List<Graph.Node> startNodes = createNodes(taskId, savedFileName, startNodeConfig, row, isPreview);
                    List<Graph.Node> endNodes = createNodes(taskId, savedFileName, endNodeConfig, row, isPreview);
                    for (Graph.Node startNode : startNodes) {
//
                        mergeNode(mapNodes, startNode);
                        for (Graph.Node endNode : endNodes) {
                            mergeNode(mapNodes, endNode);
                            //20210423  这两个if是临时演示用的，请忽略它的丑陋
                            if (startNode.getLabels().contains("侦察演训") && null != startNode.getProperties().get("基地") && endNode.getLabels().contains("装备")) {

                                String[] bases = startNode.getProperties().get("基地").toString().split(",");
                                for(String baseName : bases) {

                                    String cypher = "MATCH(n:`装备`)--(m:`军事基地`) WHERE n.名称 = '" + endNode.getName() +
                                            "' AND n.taskId = " + taskId + " AND m.名称 = '" + baseName + "' AND m.taskId = "
                                            + taskId + " RETURN n";
                                    List<GraphNeo4jVO.Node> nodes = graphProcessor.getNodeByCypher(cypher).getNodes();
                                    if (!CollectionUtils.isEmpty(nodes)) {
                                        mapNodes.remove(endNode.getId());
                                        Graph.Node node = new Graph.Node();
                                        BeanUtils.copyProperties(nodes.get(0), node);
                                        mapNodes.put(node.getId(), node);

                                        Graph.Link link = createLink(taskId, savedFileName, startNode.getId(), node.getId(),
                                                relationshipConfig, row);
                                        mergeLink(mapLinks, link);
                                    }
                                }
                            } else if (endNode.getLabels().contains("侦察演训")&& null != endNode.getProperties().get("基地") && startNode.getLabels().contains("装备")) {
                                String[] bases = startNode.getProperties().get("基地").toString().split(",");
                                for(String baseName : bases) {

                                    String cypher = "MATCH(n:`装备`)--(m:`军事基地`) WHERE n.名称 = '" + startNode.getName() +
                                            "' AND n.taskId = " + taskId + "m.名称 = '" + baseName + "' AND m.taskId = "
                                            + taskId + " RETURN n";
                                    List<GraphNeo4jVO.Node> nodes = graphProcessor.getNodeByCypher(cypher).getNodes();
                                    if (!CollectionUtils.isEmpty(nodes)) {
                                        mapNodes.remove(startNode.getId());
                                        Graph.Node node = new Graph.Node();
                                        BeanUtils.copyProperties(nodes.get(0), node);
                                        mapNodes.put(node.getId(), node);

                                        Graph.Link link = createLink(taskId, savedFileName, node.getId(), endNode.getId(),
                                                relationshipConfig, row);
                                        mergeLink(mapLinks, link);
                                    }
                                }//
                            } else {
                                Graph.Link link = createLink(taskId, savedFileName, startNode.getId(), endNode.getId(),
                                        relationshipConfig, row);
                                mergeLink(mapLinks, link);
                            }
                        }
                    }

                    // 文本文档数据关联时，事件与文档关联
                    if (fileType.equals(FileTypeEnum.WORD07.getDataTypeCode())) {
                        startNodes.addAll(endNodes);
                        for (Graph.Node node : startNodes) {
                            if (node.getLabels().contains(NodeTypeEnum.EVENT.getName())) {
                                String linkId = finalFileNode.getId() + node.getId();
                                Map<String, Object> props = new HashMap<>();
                                props.put(GraphDbConstant.PROPERTY_KEY_ID, linkId);
                                props.put(GraphDbConstant.PROPERTY_KEY_TASK_ID, taskId);

                                Graph.Link docLink = Graph.Link.builder()
                                        .id(linkId)
                                        .type("属于")
                                        .startNodeId(node.getId())
                                        .endNodeId(finalFileNode.getId())
                                        .properties(props)
                                        .build();
                                mapLinks.put(docLink.getId(), docLink);
                            }
                        }
                    }

                    // 如果开始节点是交易事件节点，记录交易事件相关节点
                    for (Graph.Node startNode : startNodes) {
                        if (startNode.getLabels().contains(GraphDbConstant.EVENT_TYPE_TRANSACTION)) {
                            transactionEventRelationNode.merge(startNode.getId(), endNodes, (oldRelationNodes, newRelationNodes) -> {
                                oldRelationNodes.addAll(newRelationNodes);
                                return oldRelationNodes;
                            });
                        } else if (startNode.getLabels().contains(GraphDbConstant.EVENT_TYPE_CALL)) {
                            callEventRelationNode.merge(startNode.getId(), endNodes, (oldRelationNodes, newRelationNodes) -> {
                                oldRelationNodes.addAll(newRelationNodes);
                                return oldRelationNodes;
                            });
                        }
                    }
                    // 如果目标节点是交易事件节点，记录交易事件相关节点
                    for (Graph.Node endNode : endNodes) {
                        if (endNode.getLabels().contains(GraphDbConstant.EVENT_TYPE_TRANSACTION)) {
                            transactionEventRelationNode.merge(endNode.getId(), startNodes, (oldRelationNodes, newRelationNodes) -> {
                                oldRelationNodes.addAll(newRelationNodes);
                                return oldRelationNodes;
                            });
                        } else if (endNode.getLabels().contains(GraphDbConstant.EVENT_TYPE_CALL)) {
                            callEventRelationNode.merge(endNode.getId(), startNodes, (oldRelationNodes, newRelationNodes) -> {
                                oldRelationNodes.addAll(newRelationNodes);
                                return oldRelationNodes;
                            });
                        }
                    }
                });

                // 交易事件的两个关联节点间 建立关系
                transactionEventRelationNode.forEach((k, v)-> {
                    if (v.size() == 2) {
                        Graph.Node nodeOne = v.get(0);
                        Graph.Node nodeTwo = v.get(1);
                        //如果第二个节点lebel是主卡号，则调换
                        if (String.join("-",nodeTwo.getLabels()).contains("主"))  {
                            nodeOne = v.get(1);
                            nodeTwo = v.get(0);
                        }
                        Map<String, Object> properties = new HashMap<>();
                        String amount = "0";
                        if (mapNodes.get(k).getProperties().containsKey(GraphDbConstant.PROPERTY_KEY_AMOUNT)) {
                            amount = mapNodes.get(k).getProperties().get(GraphDbConstant.PROPERTY_KEY_AMOUNT).toString();
                        }
                        try {
                            properties.put("amount", Double.parseDouble(amount));
                        } catch (Exception e) {
                            properties.put("amount", 0D);
                        }

                        String type = GraphDbConstant.EVENT_TYPE_TRANSACTION;

                        Graph.Link link = Graph.Link.builder()
                                .id(nodeOne.getId() + "_" + nodeTwo.getId())
                                .type(type)
//                                .mergeType("eventLinkMerge")
                                .startNodeId(nodeOne.getId())
                                .endNodeId(nodeTwo.getId())
                                .properties(properties)
                                .build();

                        mapLinks.merge(link.getId(), link, (oldLink, newLink) -> {
                            oldLink.getProperties().merge("amount", newLink.getProperties().get("amount"),
                                    (oldAmount, newAmount) -> Double.parseDouble(String.format("%.2f",
                                            (double) oldAmount + (double) newAmount)));

                            return oldLink;
                        });

                        //根据交易金额的多少来判断交易方向
                        String[] ids = link.getId().split("_");
                        if ((double) mapLinks.get(link.getId()).getProperties().get("amount") > 0) {
                            mapLinks.get(link.getId()).setStartNodeId(ids[1]);
                            mapLinks.get(link.getId()).setEndNodeId(ids[0]);
                        } else {
                            mapLinks.get(link.getId()).setStartNodeId(ids[0]);
                            mapLinks.get(link.getId()).setEndNodeId(ids[1]);
                        }
                    }
                });

                // 通话事件的两个关联节点间 建立关系
                callEventRelationNode.forEach((k, v)-> {
                    if (v.size() == 2) {
                        Graph.Node nodeOne = v.get(0);
                        Graph.Node nodeTwo = v.get(1);

                        Map<String, Object> properties = new HashMap<>();
                        properties.put("amount", 1D);

                        String type = GraphDbConstant.EVENT_TYPE_CALL;

                        Graph.Link link = Graph.Link.builder()
                                .id(nodeOne.getId() + "_" + nodeTwo.getId())
                                .type(type)
//                                .mergeType("eventLinkMerge")
                                .startNodeId(nodeOne.getId())
                                .endNodeId(nodeTwo.getId())
                                .properties(properties)
                                .build();

                        mapLinks.merge(link.getId(), link, (oldLink, newLink) -> {
                            oldLink.getProperties().merge("amount", newLink.getProperties().get("amount"),
                                    (oldAmount, newAmount) -> (double) oldAmount + (double) newAmount);

                            return oldLink;
                        });


                    }
                });
            }

        }

        List<Graph.Node> listNodes = new ArrayList<>();
        mapNodes.forEach((k, v) -> listNodes.add(v));

        List<Graph.Link> listLinks = new ArrayList<>();
        mapLinks.forEach((k, v) -> listLinks.add(v));

//        System.out.println(" 处理用时: " + stopwatch.getTime(TimeUnit.MILLISECONDS));
        return new Graph(listNodes, listLinks);
    }

    private Boolean isMatchedHeader(List<String> header, List<GraphModel> graphModels) {
        for (GraphModel model : graphModels) {
            for(NodePropertyConfig nodePropertyConfig : model.getStartNodeConfig().getNodePropertyConfigs()) {
                if(!StringUtils.isEmpty(nodePropertyConfig.getPropertyField()) &&
                        !header.contains(nodePropertyConfig.getPropertyField())) {
                    return false;
                }
            }
            for(NodePropertyConfig nodePropertyConfig : model.getEndNodeConfig().getNodePropertyConfigs()) {
                if(!StringUtils.isEmpty(nodePropertyConfig.getPropertyField()) &&
                        !header.contains(nodePropertyConfig.getPropertyField().toString())) {
                    return false;
                }
            }
            for(RelationshipPropertyConfig relationshipPropertyConfig : model.getRelationshipConfig().getRelationshipPropertyConfigs()) {
                if(!StringUtils.isEmpty(relationshipPropertyConfig.getPropertyField()) &&
                        !header.contains(relationshipPropertyConfig.getPropertyField())) {
                    return false;
                }
            }
        }
        return true;
    }

    private List<Graph.Node> createNodes(Integer taskId, String savedFileName, NodeConfig nodeConfig,
                                                 Map<String, String> row, Boolean isPreview) {
        List<Graph.Node> retValue = new ArrayList<>();
        List<String> labelList = nodeConfig.getLabels();
        String labels = String.join(",", labelList);
        String icon = "";
        if (isPreview) {
            icon = serverConfig.parseViewUrl(sysIconMapper.getIconFileByLabels(labels), 3);
        }
        // 属性
        Map<String, Object> retProperties = new HashMap<>();
        List<NodePropertyConfig> properties = nodeConfig.getNodePropertyConfigs();
        List<Property> primaryProps = new ArrayList<>();
        List<Property> displayProps = new ArrayList<>();

        Optional.ofNullable(properties).ifPresent(props -> {
            props.forEach(property -> {
                String propKey = property.getPropertyKey();
                String propFiled = property.getPropertyField();
                if (!StringUtils.isEmpty(propFiled)) {
                    String value = row.get(propFiled);
                    if (!StringUtils.isEmpty(value)) {
                        if (propKey.contains("时间") || propKey.contains("日期")) {
                            retProperties.put(propKey, TimeFormatUtil.transformToStandardFormat(value));
                        } else if (propKey.contains("金额") && NumberUtil.isNumber(value)) {
                            retProperties.put(propKey, Convert.toNumber(value));
                        } else {
                            retProperties.put(propKey, value);
                        }
                        //主键属性
                        if (property.getPrimaryKey()) {
                            primaryProps.add(new Property(propKey, value));
                        }
                        //显示的属性
                        if (null != property.getDisplay() && property.getDisplay()) {
                            displayProps.add(new Property(propKey, value));
                        }
                    }
                }
            });
        });
//        retProperties.put(GraphDbConstant.PROPERTY_KEY_PRIMARY_KEY, StringUtils.arrayToCommaDelimitedString(primaryKeys.toArray()));
//        retProperties.put(GraphDbConstant.PROPERTY_KEY_DISPLAY, StringUtils.arrayToCommaDelimitedString(displayKeys.toArray()));
        //来源
        FileName sourceFileName = new FileName(savedFileName.substring(savedFileName.indexOf("_") + 1), savedFileName);
        Set<JSONObject> sourceFileNames = Collections.singleton((JSONObject) JSON.toJSON(sourceFileName));
        retProperties.merge(GraphDbConstant.PROPERTY_KEY_SOURCE, sourceFileNames, (a, b) -> {
            Set<JSONObject> originValue = (Set<JSONObject>) a;
            originValue = new HashSet<>(originValue);
            originValue.addAll((Set<JSONObject>) b);
            return originValue;
        });

        //任务ID 必须记录
        retProperties.put(GraphDbConstant.PROPERTY_KEY_TASK_ID, taskId);
        // 默认节点id
        String id = String.valueOf((labels + row.toString() + taskId).hashCode());


        // 显示
        StringBuilder displayValue = new StringBuilder();
        for (int i = 0; i < displayProps.size(); i++) {
            displayValue.append(displayProps.get(i).getValue());
            if (i < displayProps.size() - 1) {
                displayValue.append(",");
            }
        }

        // 有主键值
        if (!primaryProps.isEmpty()) {
            // 实体 单主键，第一个主键值 可能有多个值（实体） 逗号间隔
            if (labelList.get(0).equals(NodeTypeEnum.ENTITY.getName()) && primaryProps.size() == 1) {
                String[] primaryValues = primaryProps.get(0).getValue().split(",");

                for (String primaryValue : primaryValues) {
                    if (StringUtils.isEmpty(primaryValue)) {
                        continue;
                    }

                    Map<String, Object> newProperties = new HashMap<>();
                    retProperties.forEach(newProperties::put);

                    // 主键属性
                    newProperties.put(primaryProps.get(0).getKey(), primaryValue);

                    id = String.valueOf((labels + primaryValue + taskId).hashCode());
                    if (isPreview) { // 预览时显示icon
                        if (labelList.get(1).contains("国家")) {
                            File file = new File(CommonConstant.ICON_PATH + primaryValue + ".png");
                            if (file.exists()) {
                                icon = serverConfig.parseViewUrl(primaryValue + ".png", 3);
                            }
                        } else if (labelList.get(1).contains("卡号") || labelList.get(1).contains("账号")
                                || labelList.get(1).contains("银行")) {
                            String bankIconFile = "";
                            if (primaryValue.length() > 5) {
                                List<BankCardBin> bankCardBins = bankCardBinDao.selectByBin(primaryValue.substring(0, 6));
                                if (!bankCardBins.isEmpty()) {
                                    bankIconFile = bankCardBins.get(0).getIconFile();
                                }
                            }
                            if (!StringUtils.isEmpty(bankIconFile)) {
                                icon = serverConfig.parseViewUrl(bankIconFile, 3);
                            }
                        }
                    }
                    Graph.Node retNode = new Graph.Node();
                    retNode.setLabels(labelList);
                    retNode.setId(id);
                    retNode.setIcon(icon);
                    retNode.setProperties(newProperties);
                    if (primaryValues.length == 1 && displayValue.length() > 0) {
                        retNode.setName(displayValue.toString());
                    } else {
                        retNode.setName(primaryValue);
                    }

                    retValue.add(retNode);
                }
            } else {
                id = String.valueOf((labels + primaryProps.toString() + taskId).hashCode());
                Graph.Node retNode = new Graph.Node();
                retNode.setLabels(labelList);
                retNode.setId(id);
                retNode.setIcon(icon);
                retNode.setProperties(retProperties);
                if (displayValue.length() > 0) {
                    retNode.setName(displayValue.toString());
                } else {
                    retNode.setName(primaryProps.get(0).getValue());
                }
                retValue.add(retNode);
            }
        } else {
//            Graph.Node retNode = new Graph.Node();
//            retNode.setLabels(labelList);
//            retNode.setId(id);
//            retNode.setIcon(icon);
//            retNode.setProperties(retProperties);
//            retValue.add(retNode);
        }

        return retValue;
    }

    /**
     * 合并重复结点，合并属性
     *
     * @author sx
     * @date 2019/9/26 20:06
     */
    private void mergeNode(Map<String, Graph.Node> retNodes, Graph.Node newNode) {
        String id = newNode.getId();
        retNodes.merge(id, newNode, (oldNodeValue, newNodeValue) -> {
            oldNodeValue.getProperties().forEach((k, v) -> {
                if (GraphDbConstant.PROPERTY_KEY_SOURCE.equals(k)) {
                    newNodeValue.getProperties().merge(k, v, (c, d) -> {
                        Set<JSONObject> originValue = new HashSet<>((Set<JSONObject>) c);
                        if (d.getClass() == String.class) {
                            JSONArray jaValue = JSON.parseArray((String) d);
                            for (Object o : jaValue) {
                                originValue.add((JSONObject) o);
                            }
                        } else {
                            originValue.addAll((Set<JSONObject>) d);
                        }
                        return originValue;
                    });
                }

            });
            return newNodeValue;
        });
    }

    private Graph.Link createLink(Integer taskId, String savedFileName, String startId, String endId,
                                         RelationshipConfig relationshipConfig, Map<String, String> row) {
        String relationType = relationshipConfig.getType();

        List<RelationshipPropertyConfig> properties = relationshipConfig.getRelationshipPropertyConfigs();
        Map<String, Object> retProperties = new HashMap<>();
//        AtomicReference<String> primeKey = new AtomicReference<>("");
//        AtomicReference<String> primeKeyValue = new AtomicReference<>("");
        Optional.ofNullable(properties).ifPresent(props -> {
            props.forEach(property -> {
                String propKey = property.getPropertyKey();
                String propFiled = property.getPropertyField();
                if (!StringUtils.isEmpty(propFiled)) {
                    String value = row.get(propFiled);
                    if (!StringUtils.isEmpty(value)) {
                        if (propKey.contains("时间") || propKey.contains("日期")) {
                            retProperties.put(propKey, TimeFormatUtil.transformToStandardFormat(value));
                        } else if (NumberUtil.isNumber(value)) {
                            retProperties.put(propKey, Convert.toNumber(value));
                        } else {
                            retProperties.put(propKey, value);
                        }
                    }
//                    //主键
//                    if (property.getPrimaryKey()) {
//                        primeKey.set(propKey);
//                        primeKeyValue.set(value);
//                    }
                }
            });
        });
        String id = String.valueOf((startId + endId + relationType + taskId).hashCode());

        retProperties.put(GraphDbConstant.PROPERTY_KEY_TASK_ID, taskId);
        retProperties.put(GraphDbConstant.PROPERTY_KEY_ID, id);
        return Graph.Link.builder()
                .id(id)
                .type(relationType)
                .startNodeId(startId)
                .endNodeId(endId)
                .properties(retProperties)
                .ontologyRelationshipId(relationshipConfig.getOntologyRelationshipId())
                .build();
    }


    private void mergeLink(Map<String, Graph.Link> retLinks, Graph.Link newLink) {
        String id = newLink.getId();
        retLinks.merge(id, newLink, (oldValueLink, newValueLink) -> {
            if (null != oldValueLink.getProperties()) {
                oldValueLink.getProperties().forEach((k, oldPropValue) -> {
                    if (GraphDbConstant.PROPERTY_KEY_SOURCE.equals(k)) {
                        newValueLink.getProperties().merge(k, oldPropValue, (c, d) -> {
                            Set<JSONObject> originValue = new HashSet<>((Set<JSONObject>) c);
                            if (d.getClass() == String.class) {
                                JSONArray jaValue = JSON.parseArray((String) d);
                                for (Object o : jaValue) {
                                    originValue.add((JSONObject) o);
                                }
                            } else {
                                originValue.addAll((Set<JSONObject>) d);
                            }
                            return originValue;
                        });
                    } else if (GraphDbConstant.PROPERTY_KEY_TIMES.equals(k)) {
                        // 次数
                        newValueLink.getProperties().merge(k, oldPropValue, (c, d) -> {
                            JSONArray propValue = JSONArray.parseArray((String) c);
                            if (d.getClass() == String.class) {
                                propValue.addAll(JSON.parseArray((String) d));
                            } else {
                                propValue.addAll((JSONArray) d);
                            }
                            return propValue.toJSONString();
                        });
                    } else if (!GraphDbConstant.PROPERTY_KEY_ID.equals(k)
                            && !GraphDbConstant.PROPERTY_KEY_TASK_ID.equals(k)
                            && !GraphDbConstant.PROPERTY_KEY_TIME.equals(k)) {
                        newValueLink.getProperties().merge(k, oldPropValue, (c, d) -> {
                            Set<String> propValue = new HashSet<>();
//                            if (c.getClass() == Double.class) {
//                                propValue = new HashSet<>(Collections.singleton(String.valueOf(c)));
//                            } else {
                                propValue = new HashSet<>(Collections.singleton(String.valueOf(c)));
//                            }

                            if (d.getClass() == String.class) {
                                String[] arrValue = ((String) d).replace("[", "")
                                        .replace("]", "")
                                        .split(",");
                                Collections.addAll(propValue, arrValue);
                            } else if (d.getClass() == Double.class) {
                                propValue.addAll(new HashSet<>(Collections.singleton(String.valueOf(d))));
                            } else {
                                propValue.addAll((Set<String>) d);
                            }
                            return propValue;
                        });
                    }
                });
            }
            return newValueLink;
        });
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean importData(Graph graphData, Integer taskId, Integer fileId, String modelPicContent,
                              List<GraphModel> graphModels) {
        boolean retValue = graphProcessor.saveToEmbeddedNeo4j(graphData, taskId);

        // 记录任务相关数据
        Set<List<String>> taskLabels = new HashSet<>();
        graphModels.forEach(graphModel -> {
            taskLabels.add(graphModel.getStartNodeConfig().getLabels());
            taskLabels.add(graphModel.getEndNodeConfig().getLabels());
            taskGraphData.addTaskRelationshipType(taskId, graphModel.getRelationshipConfig().getType(), graphModel.getRelationshipConfig().getOntologyRelationshipId());
        });
        taskGraphData.addTaskLabels(taskId, taskLabels);
        taskGraphData.addTaskFileModel(taskId, fileId, modelPicContent);
        taskGraphData.updateStatus(taskId, fileId, 1);
        return retValue;
    }


    private Graph generateGraphDataByEmailData(Integer taskId, List<Email> emails) {

        //用来去重
        Map<String, Graph.Node> mapNodes = new HashMap<>();
        Map<String, Graph.Link> mapLinks = new HashMap<>();

        emails.forEach(email -> {
            // 发件人
            List<String> labels = Arrays.asList("实体,邮箱".split(","));
            String id = String.valueOf((taskId + email.getFrom().getAddress()).hashCode());
            Map<String, Object> props = new HashMap<>();
            props.put("地址", email.getFrom().getAddress());
            props.put("昵称", email.getFrom().getPersonal());
            Graph.Node nodeFrom = Graph.Node.builder()
                    .labels(labels)
                    .id(id)
                    .properties(props)
                    .name(email.getFrom().getAddress())
                    .build();

            mergeNode(mapNodes, nodeFrom);

            props = new HashMap<>();
            props.put("主题", email.getSubject());
            props.put("内容", email.getContent());
            Graph.Node nodeEmailEvent = Graph.Node.builder()
                    .labels(Arrays.asList("事件,发送邮件".split(",")))
                    .id(String.valueOf((taskId + email.getContent()).hashCode()))
                    .properties(props)
                    .name(email.getSubject())
                    .build();
            mergeNode(mapNodes, nodeEmailEvent);

            email.getAttachments().forEach(att -> {

                Map<String, Object> propsAtt = new HashMap<>();
                propsAtt.put("名称", att);
                //来源
                FileName sourceFileName = new FileName(att, att);
                Set<JSONObject> sourceFileNames = Collections.singleton((JSONObject) JSON.toJSON(sourceFileName));
                propsAtt.put(GraphDbConstant.PROPERTY_KEY_SOURCE, sourceFileNames);

                if (att.endsWith(FileTypeEnum.JPG.getSuffix()) || att.endsWith(FileTypeEnum.JPEG.getSuffix())
                        || att.endsWith(FileTypeEnum.PNG.getSuffix())|| att.endsWith(FileTypeEnum.GIF.getSuffix())) {
                    propsAtt.put(GraphDbConstant.PROPERTY_KEY_ICON, "upload/" + att);
                }
                Graph.Node nodeAttachment = Graph.Node.builder()
                        .labels(Arrays.asList("文档,附件".split(",")))
                        .id(String.valueOf((taskId + att).hashCode()))
                        .properties(propsAtt)
                        .name(att)
                        .build();
                mergeNode(mapNodes, nodeAttachment);
                String linkId = nodeEmailEvent.getId() + "_" + nodeAttachment.getId();
                Graph.Link link = Graph.Link.builder()
                        .id(linkId)
                        .startNodeId(nodeEmailEvent.getId())
                        .endNodeId(nodeAttachment.getId())
                        .type("附件")
                        .build();

                mergeLink(mapLinks, link);

            });


            email.getRecipients().forEach(rec -> {
                Map<String, Object> propsTo = new HashMap<>();
                propsTo.put("地址", rec.getAddress());
                propsTo.put("昵称", rec.getPersonal());
                Graph.Node nodeTo = Graph.Node.builder()
                        .labels(labels)
                        .id(String.valueOf((taskId + rec.getAddress()).hashCode()))
                        .properties(propsTo)
                        .name(rec.getAddress())
                        .build();
                mergeNode(mapNodes, nodeTo);

                String linkId = nodeFrom.getId() + "_" + nodeTo.getId();
                Graph.Link link = Graph.Link.builder()
                        .id(linkId)
                        .startNodeId(nodeFrom.getId())
                        .endNodeId(nodeTo.getId())
                        .type("发送邮件")
                        .build();

                mergeLink(mapLinks, link);

                linkId = nodeFrom.getId() + "_" + nodeEmailEvent.getId();
                link = Graph.Link.builder()
                        .id(linkId)
                        .startNodeId(nodeFrom.getId())
                        .endNodeId(nodeEmailEvent.getId())
                        .type("发送")
                        .build();
                mergeLink(mapLinks, link);

                linkId = nodeEmailEvent.getId() + "_" + nodeTo.getId();
                link = Graph.Link.builder()
                        .id(linkId)
                        .startNodeId(nodeEmailEvent.getId())
                        .endNodeId(nodeTo.getId())
                        .type("接收")
                        .build();
                mergeLink(mapLinks, link);


            });



        });

        List<Graph.Node> listNodes = new ArrayList<>();
        mapNodes.forEach((k, v) -> listNodes.add(v));

        List<Graph.Link> listLinks = new ArrayList<>();
        mapLinks.forEach((k, v) -> listLinks.add(v));

        return new Graph(listNodes, listLinks);
    }

}
